/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
 */
package org.olap4j;

import java.sql.*;

/**
 * <p>
 * An exception describing an error accessing an OLAP database.
 * </p>
 * 
 * <p>
 * Since olap4j extends JDBC, it is natural that <code>OlapException</code> should extend JDBC's {@link SQLException}.
 * The implementation by an olap4j driver of a JDBC method which is declared to throw a SQLException may, if the driver
 * chooses, throw instead an OlapException.
 * </p>
 * 
 * <p>
 * OlapException provides some additional information to help an OLAP client identify the location of the error. This
 * information is
 * 
 * @author jhyde
 * @since Oct 23, 2006
 */
public class OlapException extends SQLException {
	private Region region;
	private Object context;

	/**
	 * Constructs an <code>OlapException</code> object with a given <code>reason</code>, <code>SQLState</code> and
	 * <code>vendorCode</code>.
	 * 
	 * @param reason
	 *            a description of the exception
	 * @param sqlState
	 *            an XOPEN or SQL 99 code identifying the exception
	 * @param vendorCode
	 *            a database vendor-specific exception code
	 */
	public OlapException(String reason, String sqlState, int vendorCode) {
		super(reason, sqlState, vendorCode);
	}

	/**
	 * Constructs an <code>OlapException</code> object with the given reason and SQLState; the <code>vendorCode</code>
	 * field defaults to 0.
	 * 
	 * @param reason
	 *            a description of the exception
	 * @param sqlState
	 *            an XOPEN or SQL 99 code identifying the exception
	 */
	public OlapException(String reason, String sqlState) {
		super(reason, sqlState);
	}

	/**
	 * Constructs an <code>OlapException</code> object with a reason; the <code>sqlState</code> field defaults to
	 * <code>null</code>, and the <code>vendorCode</code> field defaults to 0.
	 * 
	 * @param reason
	 *            a description of the exception
	 */
	public OlapException(String reason) {
		super(reason);
	}

	/**
	 * Constructs an <code>OlapException</code> object; the <code>reason</code> field defaults to null, the
	 * <code>sqlState</code> field defaults to <code>null</code>, and the <code>vendorCode</code> field defaults to 0.
	 */
	public OlapException() {
		super();
	}

	/**
	 * Constructs an <code>OlapException</code> object with a given <code>cause</code>. The <code>SQLState</code> is
	 * initialized to <code>null</code> and the vendor code is initialized to 0. The <code>reason</code> is initialized
	 * to <code>null</code> if <code>cause==null</code> or to <code>cause.toString()</code> if <code>cause!=null</code>.
	 * <p>
	 * 
	 * @param cause
	 *            the underlying reason for this <code>OlapException</code> (which is saved for later retrieval by the
	 *            <code>getCause()</code> method); may be null indicating the cause is non-existent or unknown.
	 */
	public OlapException(Throwable cause) {
		super();
		initCause(cause);
	}

	/**
	 * Constructs an <code>OlapException</code> object with a given <code>reason</code> and <code>cause</code>.
	 * 
	 * @param reason
	 *            the detail message (which is saved for later retrieval by the {@link #getMessage()} method).
	 * @param cause
	 *            the cause (which is saved for later retrieval by the {@link #getCause()} method). (A <tt>null</tt>
	 *            value is permitted, and indicates that the cause is nonexistent or unknown.)
	 */
	public OlapException(String reason, Throwable cause) {
		// Cannot call super(reason, cause) because
		// SQLException(String, Throwable) only exists from JDK 1.6.
		super(reason);
		initCause(cause);
	}

	/**
	 * Constructs an <code>OlapException</code> object with a given <code>reason</code>, <code>SQLState</code> and
	 * <code>cause</code>. The vendor code is initialized to 0.
	 * 
	 * @param reason
	 *            a description of the exception.
	 * @param sqlState
	 *            an XOPEN or SQL:2003 code identifying the exception
	 * @param cause
	 *            the underlying reason for this <code>OlapException</code> (which is saved for later retrieval by the
	 *            <code>getCause()</code> method); may be null indicating the cause is non-existent or unknown.
	 */
	public OlapException(String reason, String sqlState, Throwable cause) {
		// Cannot call SQLException(String, String, Throwable); it only
		// exists from JDK 1.6
		super(reason, sqlState);
		initCause(cause);
	}

	/**
	 * Constructs an <code>OlapException</code> object with a given <code>reason</code>, <code>SQLState</code>,
	 * <code>vendorCode</code> and <code>cause</code>.
	 * 
	 * @param reason
	 *            a description of the exception
	 * @param sqlState
	 *            an XOPEN or SQL:2003 code identifying the exception
	 * @param vendorCode
	 *            a database vendor-specific exception code
	 * @param cause
	 *            the underlying reason for this <code>OlapException</code> (which is saved for later retrieval by the
	 *            <code>getCause()</code> method); may be null indicating the cause is non-existent or unknown.
	 */
	public OlapException(String reason, String sqlState, int vendorCode, Throwable cause) {
		// Cannot call SQLException(String, String, int, Throwable); it only
		// exists from JDK 1.6
		super(reason, sqlState, vendorCode);
		initCause(cause);
	}

	/**
	 * Sets the textual region where the exception occurred.
	 * 
	 * @param region
	 *            Textual region
	 */
	public void setRegion(Region region) {
		this.region = region;
	}

	/**
	 * Returns the textual region where the exception occurred, or null if no region can be identified.
	 * 
	 * @return Region where the exception occurred
	 */
	public Region getRegion() {
		return region;
	}

	/**
	 * Sets the context where the exception occurred.
	 * 
	 * @param context
	 *            Context where the exception occurred
	 * @throws IllegalArgumentException
	 *             If context is not a {@link Cell} or a {@link Position}
	 */
	public void setContext(Object context) {
		if (!(context instanceof Cell) && !(context instanceof Position)) {
			throw new IllegalArgumentException("expected Cell or Position");
		}
		this.context = context;
	}

	/**
	 * Returns the context where the exception occurred. Typically a {@link Cell} or a {@link Position}, or null.
	 * 
	 * @return context where the exception occurred, or null
	 */
	public Object getContext() {
		return context;
	}

	/**
	 * Description of the position of a syntax or validation error in the source MDX string.
	 * 
	 * <p>
	 * Row and column positions are 1-based and inclusive. For example, in
	 * </p>
	 * 
	 * <blockquote>
	 * 
	 * <pre>
	 * SELECT { [Measures].MEMBERS } ON COLUMNS,
	 *    { } ON ROWS
	 * FROM [Sales]
	 * </pre>
	 * 
	 * </blockquote>
	 * 
	 * <p>
	 * the <code>SELECT</code> keyword occupies positions (1, 1) through (1, 6), and would have a
	 * <code>Region(startLine=1, startColumn=1,
	 * endColumn=1, endLine=6)</code>.
	 * </p>
	 */
	public static final class Region {
		public final int startLine;
		public final int startColumn;
		public final int endLine;
		public final int endColumn;

		protected Region(int startLine, int startColumn, int endLine, int endColumn) {
			this.startLine = startLine;
			this.startColumn = startColumn;
			this.endColumn = endLine;
			this.endLine = endColumn;
		}

		public String toString() {
			if (startLine == endColumn && startColumn == endLine) {
				return "line " + startLine + ", column " + startColumn;
			} else {
				return "line " + startLine + ", column " + startColumn + " through line " + endLine + ", column "
						+ endColumn;
			}
		}
	}
}

// End OlapException.java
